package turritopsis

import (
	"ebbflow/pkg/adkr"
	"log"
	"time"
)









// Protocol composes sDumbo ACS with ADKR reconfiguration following Turritopsis.
type Protocol struct {
	cfg    Config
	logger *log.Logger
    m      Metrics
}

func New(cfg Config, logger *log.Logger) *Protocol {
	if logger == nil {
		logger = log.Default()
	}
	return &Protocol{cfg: cfg, logger: logger}
}

// GetMetrics returns the metrics instance for external access
func (p *Protocol) GetMetrics() *Metrics {
	return &p.m
}

// Run executes Epochs rounds: in each epoch, run ACS to decide a batch; optionally trigger ADKR reconfiguration.
// For simplicity, ACS sid is derived as cfg.SID|epoch, and KeyRefreshOne is invoked after ACS returns.
func (p *Protocol) Run() error {
	if p.cfg.ACS == nil {
		return fmt.Errorf("ACS hook not set")
	}
    p.m = Metrics{Epochs: p.cfg.Epochs}
    p.m.Begin()
    for epoch := 0; epoch < p.cfg.Epochs; epoch++ {
        // Derive plan for this epoch (fallback to static N,F, no members, L=0)
        plan := EpochPlan{Epoch: epoch, N: p.cfg.N, F: p.cfg.F, L: 0}
        if epoch < len(p.cfg.Plans) {
            plan = p.cfg.Plans[epoch]
            if plan.N == 0 { plan.N = p.cfg.N }
            if plan.F == 0 { plan.F = p.cfg.F }
        }
        if err := plan.Validate(); err != nil { return fmt.Errorf("epoch %d invalid plan: %w", epoch, err) }
        // Dynamically inject thresholds: allow external code to map (n,f) to internal components
        if p.cfg.ThresholdSetter != nil {
            if err := p.cfg.ThresholdSetter(epoch, plan.N, plan.F); err != nil { return fmt.Errorf("set thresholds: %w", err) }
        }
        if p.cfg.BeforeEpoch != nil {
            if err := p.cfg.BeforeEpoch(epoch, plan); err != nil { return err }
        }
		sid := fmt.Sprintf("%s-epoch-%d", p.cfg.SID, epoch)
        p.logger.Printf("Turritopsis PID:%d epoch %d: starting ACS (sid=%s)", p.cfg.PID, epoch, sid)
        acsStart := time.Now()
		batch, err := p.cfg.ACS(sid, nil)
		if err != nil {
			return fmt.Errorf("ACS failed at epoch %d: %w", epoch, err)
		}
        acsEnd := time.Now()
        acsDur := acsEnd.Sub(acsStart)
		p.logger.Printf("Turritopsis PID:%d epoch %d: ACS decided %d items", p.cfg.PID, epoch, len(batch))
        p.m.Batches = append(p.m.Batches, len(batch))
        
        // Record batch size for detailed metrics
        p.m.RecordBatchSize(epoch, len(batch))

        // Inject KR for this epoch: prefer KRBuilder(plan), otherwise use static KeyRefreshOne
        // Execute ADKR at R-interval epochs
        var krDur time.Duration
        var aggSigSummary string
        var krResult *adkr.KeyRefreshOutput
        
        shouldExecuteKR := p.cfg.R > 0 && epoch%p.cfg.R == 0
        if shouldExecuteKR {
            kr := p.cfg.KeyRefreshOne
            if p.cfg.KRBuilder != nil {
                if k := p.cfg.KRBuilder(epoch, plan); k != nil { kr = k }
            }
            if kr != nil {
                p.logger.Printf("Turritopsis PID:%d epoch %d: triggering ADKR key-refresh (R=%d)", p.cfg.PID, epoch, p.cfg.R)
                krStart := time.Now()
                result, err := kr(epoch)
                if err != nil {
                    return fmt.Errorf("ADKR key-refresh failed at epoch %d: %w", epoch, err)
                }
                krDur = time.Since(krStart)
                krResult = result
                p.logger.Printf("Turritopsis PID:%d epoch %d: ADKR completed in %v", p.cfg.PID, epoch, krDur)
                // If an aggregate signature summary is available externally, AfterEpoch can fill it; use a placeholder here
                aggSigSummary = "aggSig:placeholder"
            } else {
                p.logger.Printf("Turritopsis PID:%d epoch %d: ADKR function is nil", p.cfg.PID, epoch)
            }
        } else {
            p.logger.Printf("Turritopsis PID:%d epoch %d: skipping ADKR (not R=%d interval)", p.cfg.PID, epoch, p.cfg.R)
        }
        
        if p.cfg.AfterEpoch != nil {
            _ = p.cfg.AfterEpoch(epoch, plan, krResult)
        }
        p.m.EpochsExt = append(p.m.EpochsExt, EpochRecord{Epoch: epoch, N: plan.N, F: plan.F, L: plan.L, Batches: len(batch), ACSms: acsDur.Milliseconds(), KRms: krDur.Milliseconds()})

        // Write lightweight proof entry with member hash and (placeholder) aggregate signature summary
        var mh string
        if len(plan.Members) > 0 {
            b, _ := json.Marshal(plan.Members)
            h := sha256.Sum256(b)
            mh = hex.EncodeToString(h[:8]) // show first 8 bytes for brevity
        }
        proof := ProofEntry{Epoch: epoch, N: plan.N, F: plan.F, L: plan.L, Members: plan.Members, MembersHash: mh, AggSigSummary: aggSigSummary}
        f, _ := os.OpenFile("turritopsis_proof.jsonl", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
        if f != nil {
            enc := json.NewEncoder(f)
            _ = enc.Encode(proof)
            f.Close()
        }
	}
    p.m.EndNow()
    // write metrics next to process cwd (experiment-friendly). Ignore errors silently.
    _ = p.m.WriteJSON("turritopsis_metrics.json")
    _ = p.m.WriteCSV("turritopsis_metrics.csv")
    p.logger.Printf("Turritopsis PID:%d completed %d epochs in %v", p.cfg.PID, p.cfg.Epochs, p.m.End.Sub(p.m.Start))
	return nil
}


